#include <buola/gui/containers/clistcontainer.h>

#include <buola/image/cfont.h>
#include <buola/image/cgc.h>
#include <buola/algorithm/container.h>
#include <buola/functors/predicates/unary.h>
#include <buola/widgets/cscrollbar.h>

namespace buola { namespace gui {

CListContainer::CListContainer()
    :   mNextID(1<<24)
    ,   mYScroll(0)
{
    SetBackColor(img::CColor(0.8,0.9,1.0));
}

CListContainer::~CListContainer()
{
}

TID CListContainer::AddGroup(const std::wstring &pName,TID pID)
{
    if(pID<0)
        pID=mNextID++;

    SGroup &lGroup=construct_back(mGroups);

    lGroup.mName=pName;
    lGroup.mID=pID;
    lGroup.mExpanded=true;

    mGroupsByID[pID]=&lGroup;

    //no refreshing or relayouting, since adding an empty group shouldn't affect anything
    
    return pID;
}

void CListContainer::RemoveGroup(TID pID)
{
    SGroup *lGroup=mGroupsByID[pID];

    if(!lGroup)
    {
        throw XNotFound("trying to remove non-existent group");
    }

    erase_first_if(mGroups,fn::pointer_equal_to(lGroup));
    mGroupsByID.erase(pID);

    ///\todo this should only be the internal layout, since we are a layout manager
    Relayout();
    Refresh();
}

void CListContainer::Add(const PWindow &pWindow,TID pGroup,const CRect_d &pMargins)
{
    SGroup *lGroup=mGroupsByID[pGroup];

    if(!lGroup)
    {
        throw XNotFound("trying to add window to non-existent group");
    }

    SWindow lWindow;
    lWindow.mWindow=pWindow;
    
    if(pMargins.Undefined())
        lWindow.mMargins={20,5,20,5};
    else
        lWindow.mMargins=pMargins;

    lGroup->mWindows.push_back(lWindow);

    Manage(pWindow);
}

void CListContainer::OnLayout()
{
    double lY=0.0;
    double lX=25.0;

    for(int i=0;i<mGroups.size();i++)
    {
        mGroups[i]->mY=lY;
        lY+=20;
        if(mGroups[i]->mExpanded)
        {
            for(int j=0;j<mGroups[i]->mWindows.size();j++)
            {
                SWindow &lW=mGroups[i]->mWindows[j];
                lW.mRect={lX+lW.mMargins.l,lY+lW.mMargins.t,Size().x-lW.mMargins.r,
                            lY+lW.mMargins.t+lW.mMargins.b+lW.mWindow->PreferredSize().y};
                lY+=lW.mRect.h();
            }
        }
        lY+=10;
    }

    double lScrollRange=lY-Size().y;

    if(lScrollRange>0)
    {
        if(!mVScroll)
        {
            mVScroll=new CScrollBar;
            mVScroll->eChanged.Connect(&CListContainer::OnVScroll,this,_1);
            Attach(mVScroll,ATTACH_EAST);
        }
        else
        {
            mVScroll->Show();
        }

        if(mYScroll>lScrollRange)
            mYScroll=lScrollRange;

        mVScroll->SetRange(0,(int)lScrollRange);
        mVScroll->SetStep(20,(int)Size().y);
        mVScroll->SetValue((int)mYScroll,false);
    }
    else
    {
        if(mVScroll)
        {
            mVScroll->SetRange(0,0);
            mVScroll->Hide();
        }

        mYScroll=0;
    }

    for(int i=0;i<mGroups.size();i++)
    {
        if(mGroups[i]->mExpanded)
        {
            for(int j=0;j<mGroups[i]->mWindows.size();j++)
            {
                SWindow &lW=mGroups[i]->mWindows[j];
                lW.mWindow->Map(CPoint_d(lW.mRect.l,lW.mRect.t-mYScroll),CSize_d(lW.mRect.w(),
                                                                                 lW.mWindow->PreferredSize().y));
            }
        }
        else
        {
            for(int j=0;j<mGroups[i]->mWindows.size();j++)
            {
                SWindow &lW=mGroups[i]->mWindows[j];
                lW.mWindow->Unmap();
            }
        }
    }

    Refresh();
}

void CListContainer::OnExpose(const CExposeEvent &pE)
{
    img::CGC *lGC=pE.mGC;

    lGC->SetSource(img::rgb8(115,115,115));
    lGC->SetStockFont(L"nicebold");

    for(int i=0;i<mGroups.size();i++)
    {
        if(i>0)
        {
            lGC->Path().MoveTo(CPoint_d(5,mGroups[i]->mY-5.5-mYScroll));
            lGC->Path().LineTo(CPoint_d(Size().x-5,mGroups[i]->mY-5.5-mYScroll));
            lGC->StrokePath();
        }

        CPoint_d lEP(10,mGroups[i]->mY+10-mYScroll);

        lGC->DrawText(CPoint_d(lEP.x+10,lEP.y),mGroups[i]->mName,img::ETextPos::VCENTER);

        if(mGroups[i]->mExpanded)
        {
            for(const SWindow &w : mGroups[i]->mWindows)
            {
                lGC->DrawText(CPoint_d(10,(w.mRect.t+w.mRect.b)/2),w.mWindow->Title(),img::ETextPos::VCENTER);
            }
            
            lGC->Path().MoveTo(CPoint_d(lEP.x-3.5,lEP.y-2.5));
            lGC->Path().LineTo(CPoint_d(lEP.x+4.5,lEP.y-2.5));
            lGC->Path().LineTo(CPoint_d(lEP.x+0.5,lEP.y+4.0));
            lGC->Path().Close();
        }
        else
        {
            lGC->Path().MoveTo(CPoint_d(lEP.x-2.5,lEP.y-3.5));
            lGC->Path().LineTo(CPoint_d(lEP.x-2.5,lEP.y+4.5));
            lGC->Path().LineTo(CPoint_d(lEP.x+4.0,lEP.y+0.5));
            lGC->Path().Close();
        }

        lGC->SetLineWidth(1.0);
        lGC->FillPath(true);
        lGC->StrokePath();
    }
}

void CListContainer::OnButtonDown(const CMouseEvent &pE)
{
    if(pE.mButton==EMouseButton::LEFT)
    {
        CPoint_d lPoint(pE.mPoint.x,pE.mPoint.y+mYScroll);

        for(int i=0;i<mGroups.size();i++)
        {
            if(CRect_d(0,mGroups[i]->mY,20,mGroups[i]->mY+20).Contains(lPoint))
            {
                mGroups[i]->mExpanded=!mGroups[i]->mExpanded;
                ///\todo this should only be the internal layout, since we are a layout manager
                Relayout();
                Refresh();
                break;
            }
        }
    }
}

void CListContainer::OnMouseWheel(const CMouseEvent &pE)
{
    if(pE.mScrollY!=0.0)
        mVScroll->Scroll(pE.mScrollY);
}

void CListContainer::OnChildRemoved(const PWindow &pWindow)
{
    for(int i=0;i<mGroups.size();i++)
    {
        for(int j=0;j<mGroups[i]->mWindows.size();j++)
        {
            if(mGroups[i]->mWindows[j].mWindow==pWindow)
            {
                mGroups[i]->mWindows.erase(mGroups[i]->mWindows.begin()+j);
                ///\todo this should only be the internal layout, since we are a layout manager
                Relayout();
                Refresh();
                break;
            }
        }
    }
}

void CListContainer::OnVScroll(int pPos)
{
    mYScroll=pPos;
    ///\todo this should only be the internal layout, since we are a layout manager
    Relayout();
    Refresh();
}

/*namespace gui*/ } /*namespace buola*/ }
